#lang scribble/base

@(require "shared.rkt" (for-label rackunit))

@title{@;{Units of Code}代码单元}

@; -----------------------------------------------------------------------------
@section{@;{Organization Matters}事项的组织}

@;{We often develop units of code in a bottom-up fashion with some top-down
planning. There is nothing surprising about this strategy because we build
code atop of existing libraries, which takes some experimentation, which in
turn is done in the REPL. We also want testable code quickly, meaning we
tend to write down those pieces of code first for which we can develop and
run tests. Readers don't wish to follow our development, however; they wish
to understand what the code computes without necessarily understanding all
the details.}
我们经常以自下而上的方式开发代码单元，并进行一些自上而下的规划。这个策略没有什么奇怪的，因为我们在现有的库的基础上构建代码，这需要一些尝试，而这些尝试是在REPL中完成的。我们还需要快速的可测试代码，这意味着我们需要先写下那些代码片段，然后才能开发和运行测试。然而，读者不希望关注我们的开发；他们希望理解代码计算的内容，而不必理解所有细节。

@;{So, please take the time to present each unit of code in a top-down
manner. This starts with the implementation part of a module. Put the
important functions close to the top, right below any code and comments as
to what kind of data you use. The rule also applies to classes, where you
want to expose @racket[public] methods before you tackle @racket[private]
methods. And the rule applies to units, too.}
所以，请花时间以自上而下的方式呈现每一个代码单元。这从模块的实现部分开始。将重要函数放在顶部，任何代码和注释的正下方，以了解你使用的数据类型。这个规则也适用于类，在处理@racket[private]方法之前，需要公开@racket[public]方法。这个规则也适用于单元。

@; -----------------------------------------------------------------------------
@section{@;{Size Matters}事项的规模}

@;{Keep units of code small. Keep modules, classes, functions and methods small.}
保持代码单位小。保持模块、类、函数和方法小。

@;{A module of 10,000 lines of code is too large. A module of 1,000 lines is
 tolerable. A module of 500 lines of code has the right size.}
10000行代码的模块太大。一个1000行的模块是可以容忍的。一个由500行代码组成的模块具有正确的大小。

@;{One module should usually contain a class and its auxiliary functions, which in
 turn determines the length of a good-sized class.}
一个模块通常应该包含一个类和它的辅助函数，这些函数又决定了一个好的类的长度。

@;{And a function/method/syntax-case of roughly 66 lines is usually
 acceptable. The 66 is based on the length of a screen with small font. It
 really means "a screen length." Yes, there are exceptions where functions
 are more than 1,000 lines long and extremely readable. Nesting levels and
 nested loops may look fine to you when you write code, but readers will
 not appreciate it keeping implicit and tangled dependencies in their
 mind. It really helps the reader to separate functions (with what you may
 call manual lambda lifting) into a reasonably flat organization of units
 that fit on a (laptop) screen and explicit dependencies.}
一个大约66行的函数/方法/语法的情况通常是可以接受的。66是基于小字体屏幕的长度。它真正的意思是“屏幕长度”。是的，有些函数的长度超过1000行并且可读性非常强。在编写代码时，嵌套级别和嵌套循环可能看起来不错，但读者不会喜欢在头脑中保留隐式和复杂的依赖关系。它确实有助于读者将功能（可以称之为手动lambda提升）分离成一个相当扁平的单元组织，这些单元适合（笔记本电脑）屏幕和显式依赖关系。

@;{For many years we had a limited syntax transformation language that forced
 people to create @emph{huge} functions. This is no longer the case, so we
 should try to stick to the rule whenever possible.}
多年来，我们有一种有限的语法转换语言，迫使人们创建@emph{巨型}函数。现在已经不是这样了，所以我们应该尽可能坚持这条规则。

@;{If a unit of code looks incomprehensible, it is probably too large. Break
 it up. To bring across what the pieces compute, implement or serve, use
 meaningful names; see @secref{names}.  If you can't come up with a good
 name for such pieces, you are probably looking at the wrong kind of
 division; consider alternatives.}
如果一个代码单元看起来不可理解，那么它可能太大了。把它拆开。要了解各个部分计算、实现或服务的内容，请使用有意义的名称；请参阅@secref{names}。如果你不能为这些作品起一个好名字，你可能是在寻找一种错误的划分；得考虑其他选择。

@; -----------------------------------------------------------------------------
@(define line
   @t{---------------------------------------------------------------------------------------------------})
@section{@;{Modules and their Interfaces}模块及其接口}

@;{The purpose of a module is to provide some services:}
模块的目的是提供一些服务：
@;
@centerline{@;{Equip a module with a short purpose statement.}给一个模块配备一个简短的目的说明。}
@;
@;{Often ``short'' means one line; occasionally you may need several lines.}
通常，“short”表示一行；有时可能需要几行。

@;{In order to understand a module's services, organize the module in three
sections below the purpose statement: its exports, its imports, and its
implementation:}
为了理解模块的服务，请将模块分为以下三个部分：导出、导入和实现：

@;%
@codebox[
@(begin
#reader scribble/comment-reader
 (racketmod0 #:file
 @tt{@;{good}好的}
 racket/base

;; the module implements a tv server

(provide
  ;; launch the tv server function
  tv-launch
  ;; set up a tv client to receive messages from the tv server
  tv-client)

(code:comment #, @line)
(code:comment #, @t{import and implementation section})

(require 2htdp/universe htdp/image)

(define (tv-launch)
  (universe ...))

(define (tv-client)
  (big-bang ...))
))]
@;%

@;{If you choose to use @racket[provide] with @racket[contract-out], you
 may wish to have two @racket[require] sections:}
如果你选择使用@racket[provide]和@racket[contract out]，你可能希望有两个@racket[require]部分：

@itemlist[
@item{@;{the first one, placed with the @racket[provide] section, imports the
 values needed to formulate the contracts and}
 第一个，放在@racket[provide]部分，输入制定合约和}
@item{@;{the second one, placed below the @racket[provide] section, imports
 the values needed to implement the services.}
 第二个放在@racket[provide]部分下面，导入实现服务所需的值。}
]

 @;{If your contracts call for additional concepts, define those right below
 the @racket[provide] specification:}
如果你的合约需要其他概念，请在@racket[provide]规范下面定义这些概念：

@;%
@codebox[
@(begin
#reader scribble/comment-reader
 (racketmod0 #:file
 @tt{@;{good}好的}
 racket/base

;; the module implements a tv server

(provide
  (contract-out
    ;; initialize the board for the given number of players
    [board-init        (-> player#/c plain-board/c)]
    ;; initialize a board and place the tiles
    [create-board      (-> player#/c (listof placement/c)
			   (or/c plain-board/c string?))]
    ;; create a board from an X-expression representation
    [board-deserialize (-> xexpr? plain-board/c)]))

(require xml)

(define player# 3)
(define plain-board/c
  (instanceof/c (and/c admin-board%/c board%-contracts/c)))

(define placement/c
  (flat-named-contract "placement" ...))

(code:comment #, @line)
(code:comment #, @t{import and implementation section})

(require 2htdp/universe htdp/image)

; implementation:
(define (board-init n)
  (new board% ...))

(define (create-board n lop)
  (define board (board-init n))
  ...)

(define board%
  (class ... some 900 lines ...))
))]
@;%
@;{In the preceding code snippet, @xml[] imports the @racket[xexpr?]
 predicate. Since the latter is needed to articulate the contract for
 @racket[board-deserialize], the @racket[require] line for @xml[] is a part
 of the @racket[provide] section. In contrast, the @racket[require] line
 below the lines imports an event-handling mechanism plus a simple image
 manipulation library, and these tools are needed only for the
 implementation of the provided services.}
在前面的代码片段中，@xml[]导入@racket[xexpr?]判断。由于需要后者来阐明@racket[board deserialize]的合约，@racket[require]行是@racket[provide]部分的一部分。相比之下，行下面的@racket[require]行导入了一个事件处理机制和一个简单的图像处理库，这些工具仅用于实现所提供的服务。

@;{Prefer specific export specifications over @racket[(provide (all-defined-out))].}
与@racket[(provide (all-defined-out))]相比，更喜欢特定的输出规格。

@;{A test suite section---if located within the module---should come at the
 very end, including its specific dependencies, i.e., @racket[require]
 specifications.}
一个测试套件部分——如果位于模块中——应该在最后，包括它的特定依赖项，即@racket[require]规范。

@; -----------------------------------------------------------------------------
@subsection{@;{Require}依赖}

@;{With @racket[require] specifications at the top of the implementation
 section, you let every reader know what is needed to understand the
 module.}
在实现部分的顶部有@racket[require]规范，你可以让每个读者知道理解模块需要什么。

@; -----------------------------------------------------------------------------
@subsection{@;{Provide}供应}

@(define 1/2-line
   @t{---------------------------------})


@;{A module's interface describes the services it provides; its body
 implements these services. Others have to read the interface if the
 external documentation doesn't suffice:}
模块的接口描述它供应的服务；其主体实现这些服务。如果外部文档不足以满足以下要求，则其他人必须阅读接口：

@centerline{@;{Place the interface at the top of the module.}将接口放在模块顶部。}
@;
@;{This helps people find the relevant information quickly.}
这有助于人们快速找到相关信息。

@compare[
@;%
@(begin
#reader scribble/comment-reader
(racketmod0 #:file
 @tt{@;{good}好的}
 racket

 ;; This module implements
 ;; several strategies.

 (provide
  ;; Stgy = State -> Action

  ;; Stgy
  ;; people's strategy
  human-strategy

  ;; Stgy
  ;; tree traversal
  ai-strategy)

 (code:comment #, @1/2-line)
 (code:comment #, @t{implementation})

 (require "basics.rkt")

 (define (general p)
   ... )

 ... some 100 lines ...
 (define human-strategy
   (general create-gui))

 ... some 100 lines ...
 (define ai-strategy
   (general traversal))))

@(begin
#reader scribble/comment-reader
(racketmod0 #:file
 @tt{@;{bad}差的}
 racket

 ;; This module implements
 ;; several strategies.

 (code:comment #, @1/2-line)
 (code:comment #, @t{implementation})

 (require "basics.rkt")

 ;; Stgy = State -> Action

 (define (general p)
   ... )
 ... some 100 lines ...

 (provide
  ;; Stgy
  ;; a person's strategy
  human-strategy)

 (define human-strategy
   (general create-gui))
 ... some 100 lines ...

 (provide
  ;; Stgy
  ;; a tree traversal
  ai-strategy)

 (define ai-strategy
   (general traversal))
 ... some 100 lines ...
))
]

@;{As you can see from this comparison, an interface shouldn't just
@scheme[provide] a list of names. Each identifier should come with a
purpose statement. Type-like explanations of data may also show up in a
@scheme[provide] specification so that readers understand what kind of data
your public functions work on.}
从这个比较中可以看出，接口不应该只是@scheme[provide]一个名称列表。每个标识符都应该带有用途说明。类似于类型的数据解释也可能出现在@scheme[provide]规范中，以便读者理解你的公共函数所处理的数据类型。

@;{While a one-line purpose statement for a function is usually enough, syntax
should come with a description of the grammar clause it introduces
@emph{and} its meaning.}
虽然一个函数的单行用途说明通常就足够了，但是其内容应该包含其介绍@emph{及}含义的语法子句的描述。

@codebox[
@(begin
#reader scribble/comment-reader
(racketmod0 #:file
@tt{@;{good}好的}
racket

(provide
 ;; (define-strategy (s:id a:id b:id c:id d:id)
 ;;   action:definition-or-expression)
 ;;
 ;; (define-strategy (s board tiles available score) ...)
 ;; defines a function from an instance of player to a
 ;; placement. The four identifier denote the state of
 ;; the board, the player's hand, the places where a
 ;; tile can be placed, and the player's current score.
 define-strategy)
))]

@;{Use @scheme[provide] with @racket[contract-out] for module interfaces.
 Contracts often provide the right level of specification for first-time
 readers.}
使用@scheme[provide]和@racket[contract out]作为模块接口。合约通常为第一次阅读者提供正确的规范级别。

@;{At a minimum, you should use type-like contracts, i.e., predicates that
 check for the constructor of data. They cost almost nothing, especially
 because exported functions tend to check such constraints internally
 anyway and contracts tend to render such checks superfluous.}
至少，你应该使用类似类型的合约，即检查数据构造函数的判断。它们几乎不需要花费任何成本，特别是因为导出的函数无论如何都倾向于在内部检查这些约束，而合约则倾向于使这些检查变得多余。

@;{If you discover that contracts create a performance bottleneck, please
 report the problem to the Racket developer mailing list.}
如果你发现合约造成性能瓶颈，请将问题报告给Racket开发者邮件列表。

@subsection{@;{Uniformity of Interface}接口一致性}

@;{Pick a rule for consistently naming your functions, classes, and
 methods. Stick to it. For example, you may wish to prefix all exported
 names with the name of the data type that they deal with, say
 @racket[syntax-local].}
选择一个规则来一致地命名函数、类和方法。一以贯之。例如，你可能希望在所有导出的名称前面加上它们所处理的数据类型的名称，例如@racket[syntax-local]。

@;{Pick a rule for consistently naming and ordering the parameters of your
 functions and methods. Stick to it. For example, if your module implements
 an abstract data type (ADT), all functions on the ADT should consume the
 ADT-argument first or last.}
为函数和方法的参数选择一致的命名和排序规则。一以贯之。例如，如果模块实现了抽象数据类型（ADT），则ADT上的所有函数都应首先或最后使用ADT参数。

@;{Finally pick the same name for all function/method arguments in a module
 that refer to the same kind of data---regardless of whether the module
 implements a common data structure. For example, in
 @filepath{collects/setup/scribble}, all functions use @racket[latex-dest]
 to refer to the same kind of data, even those that are not exported.}
最后，为引用同一类型数据的模块中的所有函数/方法参数选择相同的名称，而不管该模块是否实现公共数据结构。例如，在@filepath{collects/setup/scribble}中，所有函数都使用@racket[latex-dest]引用同一类型的数据，甚至是未导出的数据。

@subsection{@;{Sections and Sub-modules}小节和子模块}

@;{Finally, a module consists of sections. It is good practice to separate the
 sections with comment lines. You may want to write down purpose statements
 for sections so that readers can easily understand which part of a module
 implements which service. Alternatively, consider using the large letter
 chapter headings in DrRacket to label the sections of a module.}
最后，模块由小节组成。最好用注释行分隔各小节。你可能希望为小节写下用途说明，以便读者能够轻松理解模块的哪个小节实现了哪个服务。或者，考虑使用DrRacket中的大字母章节标题来标记模块的各个小节。

@;{With @racketmodname[rackunit], test suites can be defined within the
 module using @racket[define/provide-test-suite]. If you do so, locate the
 test section at the end of the module and @racket[require] the necessary
 pieces for testing specifically for the test suites.}
使用@racketmodname[rackunit]，可以使用@racket[define/provide-test-suite]在模块内定义测试套件。如果你这样做了，请将测试部分放在模块的末尾，然后@racket[require]专门用于测试套件的必要部件。

@;{As of version 5.3, Racket supports sub-modules. Use sub-modules to
 formulate sections, especially test sections. With sub-modules it is now
 possible to break up sections into distinct parts (labeled with the same
 name) and leave it to the language to stitch pieces together.}
从版本5.3开始，Racket支持子模块。使用子模块来制定小节，特别是测试小节。有了子模块，现在可以将小节分解成不同的部分（用相同的名称标记），并让语言将各部分缝合在一起。

@;%
@codebox[
@(begin
#reader scribble/comment-reader
 (racketmod0 #:file
 @tt{fahrenheit.rkt}
 racket

 (provide
   (contract-out
     (code:comment #, @t{@;{convert a fahrenheit temperature to a celsius}把华氏温度转换成摄氏温度})
     [fahrenheit->celsius (-> number? number?)]))

 (define (fahrenheit->celsius f)
   (/ (* 5 (- f 32)) 9))

 (module+ test
   (require rackunit)
   (check-equal? (fahrenheit->celsius -40) -40)
   (check-equal? (fahrenheit->celsius 32) 0)
   (check-equal? (fahrenheit->celsius 212) 100))
))]
@;%
 @;{If you develop your code in DrRacket, it will run the test sub-module
 every time you click ``run'' unless you explicitly disable this
 functionality in the language selection menu. If you have a file and you
 just wish to run the tests, use @tt{raco} to do so:}
如果你在DrRacket中开发代码，它将在每次单击“运行”时运行测试子模块，除非你在语言选择菜单中显式禁用此功能。如果你有一个文件，并且只想运行测试，请使用@tt{raco}执行此操作：

@verbatim[#:indent 2]{
$ raco test fahrenheit.rkt
}
 @;{Running this command in a shell will require and evaluate the test
 sub-module from the @tt{fahrenheit.rkt}.}
在shell中运行此命令将需要并计算@tt{fahrenheit.rkt}中的测试子模块。

@; -----------------------------------------------------------------------------
@section{@;{Classes & Units}类和单元}

@;{(I will write something here sooner or later.)}
（我迟早要在这里写点东西。）

@; -----------------------------------------------------------------------------
@section{@;{Functions & Methods}函数与方法}

@;{If your function or method consumes more than two parameters, consider
keyword arguments so that call sites can easily be understood.  In
addition, keyword arguments also ``thin'' out calls because function calls
don't need to refer to default values of arguments that are considered
optional.}
如果你的函数或方法使用两个以上的参数，请考虑关键字参数，以便可以容易地理解调用位置。此外，关键字参数还“精简”出调用，因为函数调用不需要引用所考虑的参数的默认值可选。

@;{Similarly, if your function or method consumes two (or more)
@emph{optional} parameters, keyword arguments are a must.}
类似地，如果函数或方法需要两个（或更多）@emph{可选的}参数，关键字参数是必须的。

@;{Write a purpose statement for your function.  If you can, add an informal
type and/or contract statement.}
为你的函数写一个用途说明。如果可以，请添加非正式类型和（或）合约声明。

@; -----------------------------------------------------------------------------
@section{@;{Contracts}合约}

@;{A contract establishes a boundary between a service provider and a service
consumer aka @defterm{server} and @defterm{client}. Due to historical
reasons, we tend to refer to this boundary as a @defterm{module boundary},
but the use of "module" in this phrase does @emph{not} only refer to
file-based or physical Racket modules. Clearly, @defterm{contract boundary}
is better than module boundary because it separates the two concepts.}
合约在服务提供者和服务使用者之间建立了一个边界，即@defterm{服务端（server）}和@defterm{客户端（client）}。由于历史原因，我们倾向于将这个边界称为@defterm{模块边界（module boundary）}，但是在这个短语中使用“module”只指基于文件或物理的Racket模块。显然，@defterm{合约边界（contract boundary）}比模块边界要好，因为它分离了这两个概念。

@;{When you use @racket[provide] with @racket[contract-out] at the module
level, the boundary of the physical module and the contract boundary
coincide.}
在模块级使用@racket[provide]和@racket[contract-out]时，物理模块的边界和合约边界重合。

@;{When a module becomes too large to manage without contracts but you do not
wish to distribute the source over several files, you may wish to use one
of the following two constructs to erect contract boundaries internal to
the physical module:}
当模块变得太大，无法在没有合约的情况下进行管理，但你不希望将源代码分发到多个文件上时，你可能希望使用以下两个构造之一在物理模块内部建立合约边界：
@itemlist[
@item{@racket[define/contract]}
@item{@racket[module]@;{, as in submodule.}，在子模块中。}
]

@;{Using the first one, @racket[define/contract], is like using
@racket[define] except that it is also possible to add a contract between
the header of the definition and its body. The following code display shows
a file that erects three internal contract boundaries: two for plain
constants and one for a function.}
使用第一个@racket[define/contract]就像使用@racket[define]只不过也可以在定义的头和它的主体之间添加一个合约。下面的代码显示了一个文件，该文件建立了三个内部协定边界：两个用于普通常量，一个用于函数。

@;%
@codebox[
@(begin
#reader scribble/comment-reader
 (racketmod0 #:file
 @tt{celsius.rkt}
 racket

(define/contract AbsoluteC real? -273.15)
(define/contract AbsoluteF real? -459.67)

(define/contract (celsius->fahrenheit c)
  (code:comment #, @t{@;{convert a celsius temperature to a fahrenheit temperature}把摄氏温度转换成华氏温度})
  (-> (and/c real? (>=/c AbsoluteC))
      (and/c real? (>=/c AbsoluteF)))
  ;; -- IN --
  (+ (* 9/5 c) 32))

(module+ test
  (require rackunit)
  (check-equal? (celsius->fahrenheit -40) -40)
  (check-equal? (celsius->fahrenheit 0) 32)
  (check-equal? (celsius->fahrenheit 100) 212))
))]
@;%

@;{To find out how these contract boundaries work, you may wish to conduct
som}
为了了解这些合约边界是如何工作的，你可能希望执行ｓom
@itemlist[#:style 'ordered
@item{@;{Add the following line to the bottom of the file:}在文件底部添加以下行：
@;%
@(begin
#reader scribble/comment-reader
(racketblock
(celsius->fahrenheit -300)
))
@;%
@;{Save to file and observe how the contract system blames this line and what
the blame report tells you.}保存到文件中，观察合约系统如何归咎这一行，以及归咎报告告诉你的内容。}

@item{@;{Replace the body of the @racket[celsius->fahrenheit] function with
@racketblock[(sqrt c)]
Once again, run the program and study the contract
exceptions, in particular observe which party gets blamed.}再次用@racketblock[(sqrt c)]替换@racket[celsius->fahrenheit]函数的主体，运行程序并研究合约例外情况，特别是观察哪一方受到归咎。}

@item{@;{Change the right-hand side of @racket[AbsoluteC] to
@racket[-273.15i], i.e., a complex number. This time a different contract
party gets blamed.}将@racket[AbsoluteC]的右侧改为@racket[-273.15i]，即复数。这次是另一个合约方受到归咎。}
]
@;{The screen shot below shows that @racket[define/contract] works for
mutually recursive functions with modules. This capability is unique to
@racket[define/contract].}
下面的屏幕截图显示@racket[define/contract]可用于具有模块的相互递归函数。这种能力是@racket[define/contract]独有的。

@image["mut-rec-contracts.png" #:scale .8]{@;{Mutually recursive functions with contracts}带合约的互递归函数}

@;{In contrast, submodules act exactly like plain modules when it comes to
contract boundaries. Like @racket[define/contract], a submodule establishes
a contract boundary between itself and the rest of the module. Any value
flow between a client module and the submodule is governed by
contracts. Any value flow within the submodule is free of any constraints.}
相比之下，子模块在涉及合约边界时的行为与普通模块完全相同。与@racket[define/contract]一样，子模块在自己和模块的其余部分之间建立了一个合约边界。客户端模块和子模块之间的任何值流都由@racket[define/contract]控制。子模块中的任何值流都不受任何约束。

@codebox[
@(begin
#reader scribble/comment-reader
 (racketmod0 #:file
 @tt{graph-traversal.rkt}
 racket
 ...
 (module traversal racket
   (provide
    (contract-out
     (find-path (-> graph? node? node? (option/c path?)))))

   (require (submod ".." graph) (submod ".." contract))

   (define (find-path G s d (visited history0))
     (cond
       [(node=? s d) '()]
       [(been-here? s visited) #f]
       [else (define neighbors (node-neighbors G s))
	     (define there (record s visited))
	     (define path (find-path* G neighbors d there))
	     (if path (cons s path) #f)]))

   (define (find-path* G s* d visited)
     (cond
       [(empty? s*) #f]
       [else (or (find-path G (first s*) d visited)
		 (find-path* G (rest s*) d visited))]))

   (define (node-neighbors G n)
     (rest (assq n G))))

 (module+ test
   (require (submod ".." traversal) (submod ".." graph))
   (find-path G 'a 'd))
))]
@;%

@;{Since modules and submodules cannot refer to each other in a mutual
recursive fashion, submodule contract boundaries cannot enforce constraints
on mutually recursive functions. It would thus be impossible to distribute
the @racket[find-path] and @racket[find-path*] functions from the preceding
code display into two distinct submodules.}
由于模块和子模块不能以相互递归的方式相互引用，子模块合约边界不能对相互递归的函数强制约束。因此不可能分配前面代码中的@racket[find-path]和@racket[find-path*]函数显示为两个不同的子模块。
